package syncx

import (
	"sync"
	"sync/atomic"
	"time"
)

type IDLockerV0 struct {
	cleanupDuration time.Duration //多长时间未使用的锁可以清除,前提未被引用
	lockMap         sync.Map      // 存储 ID 和对应的锁信息
}

type lockInfo struct {
	mutex   *sync.Mutex
	lastUse time.Time
	refs    int32 // 引用计数
}

func NewIDLockerV0() *IDLockerV0 {
	return NewIDLockerV0WithCleanupDuration(time.Minute * 30)
}

func NewIDLockerV0WithCleanupDuration(cleanupDuration time.Duration) *IDLockerV0 {
	l := &IDLockerV0{
		cleanupDuration: cleanupDuration,
	}
	// 启动清理协程
	go l.cleanup()
	return l
}

func (l *IDLockerV0) Lock(id string) {
	value, _ := l.lockMap.LoadOrStore(id, &lockInfo{
		mutex:   &sync.Mutex{},
		lastUse: time.Now(),
		refs:    0,
	})
	info := value.(*lockInfo)

	// 原子操作增加引用计数
	atomic.AddInt32(&info.refs, 1)
	info.mutex.Lock()
	info.lastUse = time.Now()
}

func (l *IDLockerV0) Unlock(id string) {
	if value, ok := l.lockMap.Load(id); ok {
		info := value.(*lockInfo)
		info.mutex.Unlock()
		info.lastUse = time.Now()

		// 原子操作减少引用计数
		if atomic.AddInt32(&info.refs, -1) == 0 {
			// 当引用计数为0时，可以考虑删除这个锁
			// 但需要确保没有其他并发操作正在使用
			time.AfterFunc(time.Minute, func() {
				if atomic.LoadInt32(&info.refs) == 0 {
					l.lockMap.Delete(id)
				}
			})
		}
	}
}

// cleanup 定期清理长时间未使用的锁
func (l *IDLockerV0) cleanup() {
	ticker := time.NewTicker(l.cleanupDuration / 3)
	defer ticker.Stop()

	for range ticker.C {
		now := time.Now()
		l.lockMap.Range(func(key, value interface{}) bool {
			info := value.(*lockInfo)
			// 如果锁超过30分钟未使用且引用计数为0，则删除
			if now.Sub(info.lastUse) > l.cleanupDuration && atomic.LoadInt32(&info.refs) == 0 {
				l.lockMap.Delete(key)
			}
			return true
		})
	}
}

// TryLock 尝试获取锁，如果无法立即获取则返回false
func (l *IDLockerV0) TryLock(id string) bool {
	value, _ := l.lockMap.LoadOrStore(id, &lockInfo{
		mutex:   &sync.Mutex{},
		lastUse: time.Now(),
		refs:    0,
	})
	info := value.(*lockInfo)

	if !info.mutex.TryLock() {
		return false
	}

	atomic.AddInt32(&info.refs, 1)
	info.lastUse = time.Now()
	return true
}

// LockWithTimeout 尝试在指定时间内获取锁
func (l *IDLockerV0) LockWithTimeout(id string, timeout time.Duration) bool {
	deadline := time.Now().Add(timeout)
	for time.Now().Before(deadline) {
		if l.TryLock(id) {
			return true
		}
		time.Sleep(time.Millisecond)
	}
	return false
}
